package edu.stanford.travelhard;

import java.io.File;
import java.io.IOException;
import java.util.Date;

import edu.stanford.travelhard.audio.AudioManager;
import edu.stanford.travelhard.camera.NoSearchActivity;
import edu.stanford.travelhard.camera.Util;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.DialogInterface.OnClickListener;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;
import android.widget.ToggleButton;

/**
 * Main activity for viewing, adding audio to, and publishing a Media
 * object. 
 */
public class Annotate extends NoSearchActivity implements View.OnClickListener,
		SeekBar.OnSeekBarChangeListener {
	
	/* Activity result codes */
	public static final int RESULT_DELETED = Activity.RESULT_FIRST_USER;
	public static final int RESULT_PUBLISHED = Activity.RESULT_FIRST_USER + 1;
	
	/* Handler codes */
	public static final int UPDATE_PLAYBACK_BAR = 3;
	public static final int UPDATE_RECORD_TIMER = 4;
	public static final int PLAYBACK_FINISHED = 5;
	
	/* Image dimensions */
	private static final int IMAGE_WIDTH = 512;
	private static final int IMAGE_HEIGHT = 384;
	private static final int IMAGE_SAMPLE_SIZE = 2;
	
	private Media			mMedia;
	private File            mAudioFile;
	private File            mPhotoFile;
	private AudioManager	mAudioManager;
	private AnnotateHandler mHandler = new AnnotateHandler();
	
	/* Image manipulation UI */
	private ImageView mPhotoView;
	private ImageButton mDeleteButton;
	private ImageButton mInfoButton;
	private Button mPublishButton;
	private Button mSaveButton;
	
	/* Audio manipulation UI */
	private ToggleButton mPlayButton;
	private SeekBar mPlaybackBar;
	private ToggleButton mRecordButton;
	private TextView  mRecordTimer;
	
	/* Publishing stuff */
	private ProgressDialog mPublishProgress;
	private Thread mPublishThread;
	
	private static final boolean DEBUG = true;
	
	/**
	 * Handles asynchronous messages sent to this Activity.
	 */
	private class AnnotateHandler extends Handler {
		
		public void handleMessage(Message msg) {
			switch (msg.what) {
			case Publisher.PUBLISH_SUCCEEDED:
			case Publisher.PUBLISH_FAILED:
				onPublishComplete(msg);
				break;
			case PLAYBACK_FINISHED:
				mPlayButton.setChecked(false);
				mPlaybackBar.setProgress(0);
				mPlaybackBar.setMax(0);
				
				// don't want to reveal record button
				// if media is already published
				mRecordButton.setChecked(mMedia.isPublished());
				break;
			case UPDATE_PLAYBACK_BAR:
				if (mPlaybackBar.getMax() == 0) {
					mPlaybackBar.setMax(msg.arg2);
				}
				mPlaybackBar.setProgress(msg.arg1);
				break;
			case UPDATE_RECORD_TIMER:
				String text = (String) msg.obj;
				mRecordTimer.setText(text);
				break;
			}
    	}
		
		/**
		 * Handle publish completion. On failure, show an alert to the
		 * user. On success, return to the library.
		 * 
		 * @param msg
		 */
		private void onPublishComplete(Message msg) {
			Log.i("ANNOTATE", "Publish complete");
			mPublishProgress.dismiss();
			mPublishThread = null;
			
			if (msg.what == Publisher.PUBLISH_FAILED) {
				final AlertDialog.Builder builder = new AlertDialog.Builder(Annotate.this);
				builder.setTitle("Publish failed");
				builder.setMessage((String) msg.obj);
				builder.setNeutralButton("OK", new OnClickListener() {
					public void onClick(DialogInterface dialog, int which) {
						/* no-op */
					}
				});
				builder.show();
			} else {
				// the publisher thread just updated its own copy,
				// so we need to update ours
				mMedia.setTimePublished((Date) msg.obj);
				
				Intent intent = new Intent();
		    	intent.putExtra("media", mMedia);
		    	setResult(RESULT_PUBLISHED, intent);
		    	finish();
			}
		}
	}
	
	/** Called when the activity is first created. */
    public void onCreate(Bundle savedInstanceState) {
    	super.onCreate(savedInstanceState);
    	
    	setContentView(R.layout.annotate);
    	
    	Bundle extras = getIntent().getExtras();
    	if (extras != null) {
    		mMedia = (Media) extras.get("media");
    	}
    	
    	if (mMedia == null ||
    		mMedia.getAudioFilename() == null ||
    		mMedia.getPhotoFilename() == null) {
    		cancel();
    	}
    	
   		mAudioFile = new File(mMedia.getAudioFilename());
   		mPhotoFile = new File(mMedia.getPhotoFilename());
    	
    	/* Set up photo UI */
    	mPhotoView = (ImageView) findViewById(R.id.annotator_photo);
    	
    	/* Set up image UI */
    	mDeleteButton = (ImageButton) findViewById(R.id.annotator_delete);
    	mDeleteButton.setOnClickListener(this);
    	mInfoButton = (ImageButton) findViewById(R.id.annotator_info);
    	mInfoButton.setOnClickListener(this);
    	mPublishButton = (Button) findViewById(R.id.annotator_publish);
    	mPublishButton.setOnClickListener(this);
    	mSaveButton = (Button) findViewById(R.id.annotator_save);
    	mSaveButton.setOnClickListener(this);
    	
    	/* Set up audio UI */
    	mPlayButton = (ToggleButton) findViewById(R.id.annotator_play);
    	mPlayButton.setOnClickListener(this);
    	mPlaybackBar = (SeekBar) findViewById(R.id.annotator_playback);
    	mPlaybackBar.setOnSeekBarChangeListener(this);
    	mRecordButton = (ToggleButton) findViewById(R.id.annotator_record);
    	mRecordButton.setOnClickListener(this);
    	mRecordTimer = (TextView) findViewById(R.id.annotator_record_timer);
    	
    	loadPhoto();
    }
    
    protected void onPause() {
    	super.onPause();
    	
    	// We need to get rid of the AudioManager so others
    	// can use it (and so we can re-open it later)
    	mAudioManager.destroy();
    	mAudioManager = null;
    	
    	// Get rid of any remaining AudioManager messages in
    	// our MessageHandler queue
    	mHandler.removeMessages(PLAYBACK_FINISHED);
    	mHandler.removeMessages(UPDATE_PLAYBACK_BAR);
    	mHandler.removeMessages(UPDATE_RECORD_TIMER);
    
    	if (mPublishThread != null && mPublishThread.isAlive()) {
    		// TODO: send a message to tell it to die?
    	}
    	
    	if (mPublishProgress != null && mPublishProgress.isShowing()) {
    		mPublishProgress.dismiss();
    	}
    }
    
    protected void onResume() {
    	super.onResume();
    	
    	// Create our AudioManager
    	if (mAudioManager == null) {
    		mAudioManager = new AudioManager(mMedia.getAudioFilename(), this, mHandler);
    	}
    	
    	// Reset our buttons' proper state
    	resetUI();
    }
    
    /**
     * Overload the back button to just cancel
     */
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            cancel();
        }
        return super.onKeyDown(keyCode, event);
    }
    
    /**
     * Save the image bitmap in case of rotation so we can reload faster.
     */
    public Object onRetainNonConfigurationInstance() {
    	return ((BitmapDrawable) mPhotoView.getDrawable()).getBitmap();
    }
    
    private void cancel() {
    	setResult(Activity.RESULT_CANCELED);
    	finish();
    }
    
    /**************************/
    /** UI loading functions **/
    /**************************/
    
    /**
     * Attempts to load the photo first from a stored bitmap;
     * otherwise creates the bitmap from the file. If the file
     * is empty or if the bitmap can't be created, then we just
     * finish this activity.
     */
    private void loadPhoto() {
    	if (!isFileEmpty(mPhotoFile)) {
    		Bitmap bm = null;
    		final Object data = getLastNonConfigurationInstance();
            if (data == null) {
				bm = Util.makeBitmap(mPhotoFile, mMedia.getPhotoOrientation(),
						IMAGE_WIDTH, IMAGE_HEIGHT, IMAGE_SAMPLE_SIZE);
            } else {
            	bm = (Bitmap) data;
            }
            if (bm != null) {
            	mPhotoView.setImageBitmap(bm);
            	return;
            }
    	}
    	
    	// We couldn't load the photo, so it doesn't
    	// make sense to continue with the activity
    	cancel();
    }
    
    /**
     * Sets the buttons back into their proper initial
     * configuration.
     */
    private void resetUI() {
    	// these always start out like this
    	mDeleteButton.setVisibility(View.VISIBLE);
    	mInfoButton.setVisibility(View.VISIBLE);
    	mSaveButton.setVisibility(View.VISIBLE);
    	mRecordTimer.setVisibility(View.GONE);
    	
    	// do this once so we don't have to open
    	// the file more than once to get length
    	boolean noAudio = isFileEmpty(mAudioFile);
    	
    	// don't display playback buttons if no audio
    	// to play back
    	if (noAudio) {
    		mPlayButton.setVisibility(View.INVISIBLE);
    		mPlaybackBar.setVisibility(View.INVISIBLE);
    		mRecordButton.setVisibility(View.VISIBLE);
    	} else {
    		mPlayButton.setVisibility(View.VISIBLE);
    		mPlayButton.setChecked(false);
    		mPlaybackBar.setVisibility(View.VISIBLE);
    		mPlaybackBar.setProgress(0);
    		mPlaybackBar.setMax(0);
    		
    		// leave the record button on "record"
    		mRecordButton.setVisibility(View.VISIBLE);
    		mRecordButton.setChecked(false);
    	}
    	
    	// if the media's been published, then there's
    	// some additional configuring to do.
    	if (mMedia.isPublished()) {
    		mPublishButton.setVisibility(View.INVISIBLE);
    		if (noAudio) {
    			mRecordButton.setVisibility(View.INVISIBLE);
    		} else {
    			mRecordButton.setVisibility(View.VISIBLE);
    			mRecordButton.setChecked(true);
    		}
    	}
    }

    /******************************************/
    /** View.OnClick Listener implementation **/
    /******************************************/
	public void onClick(View v) {
		switch (v.getId()) {
		case R.id.annotator_delete:
			ensureDeleteDesired();
			break;
		case R.id.annotator_info:
			showInfo();
			break;
		case R.id.annotator_play:
			if (mPlayButton.isChecked()) {
				playAudio();
			} else {
				pauseAudio();
			}
			break;
		case R.id.annotator_publish:
			ensurePublishDesired();
			break;
		case R.id.annotator_record:
			if (mRecordButton.isChecked()) {
				ensureRecordingDesired();
			} else if (mRecordTimer.getVisibility() == View.VISIBLE) {
				stopRecording();
			} else {
				stopAudio();
			}
			break;
		case R.id.annotator_save:
			save();
			break;
		}
		
	}
	
	/*************************************/
	/** SeekBar Listener implementation **/
	/*************************************/
	public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
		if (fromUser) {
			mAudioManager.seekTo(progress);
		} else {
			seekBar.setProgress(progress);
		}
		
	}
	public void onStartTrackingTouch(SeekBar seekBar) { /* no-op */ }
	public void onStopTrackingTouch(SeekBar seekBar) { /* no-op */ }
	
	/***************************/
	/** UI handling functions **/
	/***************************/
	private void ensureDeleteDesired() {
		// TODO: ensure playback and recording stopped
		
		final AlertDialog.Builder builder = new AlertDialog.Builder(this);
		builder.setTitle("Delete");
		builder.setMessage("Are you sure you want to delete this media?");
		builder.setPositiveButton("Yes", new OnClickListener() {
			public void onClick(DialogInterface dialog, int which) {
				delete();
			}
		});

		builder.setNegativeButton("No", new OnClickListener() {
			public void onClick(DialogInterface dialog, int which) {
				/* no-op */
			}
		});

		builder.show();
	}
	
	private void delete() {
    	if (mPhotoFile != null && mPhotoFile.exists()) mPhotoFile.delete();
    	if (mAudioFile != null && mAudioFile.exists()) mAudioFile.delete();
 
    	DatabaseHelper dbh = TravelCore.getDBH();
    	dbh.open();
    	dbh.deleteMedia(mMedia.getDBID());
    	dbh.close();
    	
    	Intent intent = new Intent();
    	intent.putExtra("media", mMedia);
    	setResult(RESULT_DELETED, intent);
    	finish();
	}
	
	private void showInfo() {
		String timeCaptured = "Time captured: " + mMedia.getTimeCaptured().toString();
		String timePublished = "Time published: ";
		if (mMedia.isPublished()) {
			timePublished += mMedia.getTimeCaptured().toString();
		} else {
			timePublished += "hopefully soon!";
		}
		String location = "Latitude: " + mMedia.getLat();
		location += "\nLongitude: " + mMedia.getLng();
		location += "\nAltitude: " + mMedia.getAlt();
		
		final CharSequence[] details = { timeCaptured, location, timePublished };
		
		final AlertDialog.Builder builder = new AlertDialog.Builder(this);
		builder.setTitle("Media info");
		builder.setItems(details, null);
		builder.setNeutralButton("OK", new OnClickListener() {
			public void onClick(DialogInterface dialog, int which) {
				/* no-op */
			}
		});
		builder.show();
	}
	
	private void ensurePublishDesired() {
		// TODO: ensure playback and recording stopped
		
		String message = "Are you sure you want to publish?";
		if (isFileEmpty(mAudioFile)) {
			message += " You haven't recorded any audio yet.";
		}
		final AlertDialog.Builder builder = new AlertDialog.Builder(this);
		builder.setTitle("Publish");
		builder.setMessage(message);
		builder.setPositiveButton("Yes", new OnClickListener() {
			public void onClick(DialogInterface dialog, int which) {
				publish();
			}
		});

		builder.setNegativeButton("No", new OnClickListener() {
			public void onClick(DialogInterface dialog, int which) {
				/* no-op */
			}
		});

		builder.show();
	}
	
	private void publish() {
		if (isNetworkAvailable()) {
			mPublishThread = new Thread(new Publisher(mMedia, mHandler));
			mPublishThread.start();
			mPublishProgress = ProgressDialog.show(Annotate.this, "", "Publishing. Please wait...", true);
		} else {
			final AlertDialog.Builder builder = new AlertDialog.Builder(this);
			builder.setTitle("No Network Connectivity");
			builder.setMessage("Cannot connect to the Internet. Please try again later.");
			builder.setNeutralButton("OK", new OnClickListener() {
				public void onClick(DialogInterface dialog, int which) {
					/* no-op */
				}
			});
			builder.show();
		}
	}
	
	private void save() {
		// TODO: ensure playback and recording stopped
		
		// TODO: implement saving
		// for now, just back out to library
		
		Intent intent = new Intent();
		intent.putExtra("media", mMedia);
		setResult(Activity.RESULT_OK, intent);
		finish();
	}
	
	private void playAudio() {
		if (DEBUG) {
			Util.Assert(!isFileEmpty(mAudioFile));
			Util.Assert(mPlayButton.isChecked());
			Util.Assert(mPlayButton.getVisibility() == View.VISIBLE);
			Util.Assert(mPlaybackBar.getVisibility() == View.VISIBLE);
			Util.Assert(mRecordButton.getVisibility() == View.VISIBLE);
			Util.Assert(mRecordTimer.getVisibility() == View.GONE);
			Util.Assert(!mAudioManager.inRecordAudioMode());
		}
		mRecordButton.setVisibility(View.VISIBLE);
		mRecordButton.setChecked(true);
		mAudioManager.playAudio();
	}
	
	private void pauseAudio() {
		if (DEBUG) {
			Util.Assert(!isFileEmpty(mAudioFile));
			Util.Assert(!mPlayButton.isChecked());
			Util.Assert(mPlayButton.getVisibility() == View.VISIBLE);
			Util.Assert(mPlaybackBar.getVisibility() == View.VISIBLE);
			Util.Assert(mRecordButton.isChecked());
			Util.Assert(mRecordButton.getVisibility() == View.VISIBLE);
			Util.Assert(mRecordTimer.getVisibility() == View.GONE);
			Util.Assert(mAudioManager.inPlayAudioMode());
			Util.Assert(!mAudioManager.inRecordAudioMode());
		}
		mAudioManager.pauseAudio();
	}
	
	private void stopAudio() {
		if (DEBUG) {
			Util.Assert(!isFileEmpty(mAudioFile));
			Util.Assert(mPlayButton.getVisibility() == View.VISIBLE);
			Util.Assert(mPlaybackBar.getVisibility() == View.VISIBLE);
			Util.Assert(!mRecordButton.isChecked());
			Util.Assert(mRecordButton.getVisibility() == View.VISIBLE);
			Util.Assert(mRecordTimer.getVisibility() == View.GONE);
			Util.Assert(!mAudioManager.inRecordAudioMode());
		}
		
		if (mAudioManager.inPlayAudioMode()) {
			// stop the playback
			mAudioManager.stopAudio();

			mPlayButton.setChecked(false);
			mPlaybackBar.setProgress(0);
			mPlaybackBar.setMax(0);
		}
		
		// don't want to reveal record button
		// if media is already published
		mRecordButton.setChecked(mMedia.isPublished());
	}
	
	private void ensureRecordingDesired() {
		if (!isFileEmpty(mAudioFile)) {
			final AlertDialog.Builder builder = new AlertDialog.Builder(this);
			builder.setTitle("Record");
			builder.setMessage("Are you sure you want to overwrite this recording?");
			builder.setPositiveButton("Yes", new OnClickListener() {
				public void onClick(DialogInterface dialog, int which) {
					startRecording();
				}
			});

			builder.setNegativeButton("No", new OnClickListener() {
				public void onClick(DialogInterface dialog, int which) {
					mRecordButton.setChecked(false);
				}
			});
			builder.show();
		} else {
			startRecording();
		}
	}
	
	private void startRecording() {
		if (DEBUG) {
			Util.Assert(!mMedia.isPublished());
			Util.Assert(mRecordButton.isChecked());
			Util.Assert(mRecordButton.getVisibility() == View.VISIBLE);
			Util.Assert(mRecordTimer.getVisibility() == View.GONE);
			Util.Assert(!mAudioManager.inPlayAudioMode());
			Util.Assert(!mAudioManager.inRecordAudioMode());
		}
		try {
			mPlayButton.setVisibility(View.INVISIBLE);
			mPlaybackBar.setVisibility(View.GONE);
			mRecordTimer.setText("0:00");
			mRecordTimer.setVisibility(View.VISIBLE);
			
			// ensure we've got an audio file
			// before going ahead and recording
			if (mAudioFile != null) {
				if (!mAudioFile.exists()) {
					File mediaFolder = mAudioFile.getParentFile();
					if (mediaFolder == null) {
						mediaFolder = new File(mAudioFile.getParent());
					}
					if (!mediaFolder.exists()) {
						if (!mediaFolder.mkdirs()) {
							throw new IOException("Couldn't create audio file!");
						}
					}
					mAudioFile.createNewFile();
				}
				mAudioManager.startRecording();
			}	
		} catch (IOException e) {
			e.printStackTrace();		
			mRecordButton.setChecked(false);
			mRecordTimer.setVisibility(View.GONE);
			if (!isFileEmpty(mAudioFile)) {
				mPlayButton.setChecked(false);
				mPlayButton.setVisibility(View.VISIBLE);
				mPlaybackBar.setProgress(0);
				mPlaybackBar.setMax(0);
				mPlaybackBar.setVisibility(View.VISIBLE);
			}
		}
	}
	
	private void stopRecording() {
		if (DEBUG) {
			Util.Assert(!mMedia.isPublished());
			Util.Assert(mPlayButton.getVisibility() == View.INVISIBLE);
			Util.Assert(mPlaybackBar.getVisibility() == View.GONE);
			Util.Assert(!mRecordButton.isChecked());
			Util.Assert(mRecordButton.getVisibility() == View.VISIBLE);
			Util.Assert(mRecordTimer.getVisibility() == View.VISIBLE);
			Util.Assert(!mAudioManager.inPlayAudioMode());
			Util.Assert(mAudioManager.inRecordAudioMode());
		}
		
		mAudioManager.stopRecording();
		
		// hide the record timer
		mRecordTimer.setVisibility(View.GONE);
		
		// show playback if we've now got audio
		// to play back to user
		if (!isFileEmpty(mAudioFile)) {
			mPlayButton.setVisibility(View.VISIBLE);
			mPlayButton.setChecked(false);
			mPlaybackBar.setVisibility(View.VISIBLE);
			mPlaybackBar.setProgress(0);
			mPlaybackBar.setMax(0);
		}
	}
	
	/*****************************/
	/** Other utility functions **/
	/*****************************/
	private boolean isFileEmpty(File file) {
		return (file == null || !file.exists() || file.length() == 0);
	}
	
	private boolean isNetworkAvailable() {
	    ConnectivityManager connectivityManager = 
	    	(ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
	    NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
	    return activeNetworkInfo != null;
	}
}
